// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Reflection.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework
{
    #region Using Directives

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.InteropServices;

    using mscoree;

    #endregion

    /// <summary>
    /// The reflection.
    /// </summary>
    public class Reflection
    {
        #region Public Methods

        /// <summary>
        /// Copy properties from one object to another 
        /// The source and destination object don't have to be of the same type
        /// Only Properties does. Constraints are :
        /// - Properties with the same name and same type
        /// - Readable in sourceObject and writable in destinationObject
        /// </summary>
        /// <param name="sourceObject">
        /// The sourceObject.
        /// </param>
        /// <param name="destinationObject">
        /// The destinationObject.
        /// </param>
        public static void CopyProperties(object sourceObject, object destinationObject)
        {
            var sourceProps = GetReadProperties(sourceObject);
            var destProps = GetReadWriteProperties(destinationObject);
            var unionProperties = from fromp in sourceProps
                                  from destp in destProps
                                  where fromp.Name == destp.Name && fromp.Name != "ID"
                                  select new[] { fromp, destp };

            foreach (var propertyPair in unionProperties)
            {
                var fromp = propertyPair[0];
                var destp = propertyPair[1];

                // Do it
                Devel.NoThrow(
                    delegate
                        {
                            // If is an enum
                            if (fromp.PropertyType.BaseType.Equals(typeof(Enum)))
                            {
                                if (fromp.Name.Equals("Currency1"))
                                {
                                }

                                var enuValue = fromp.GetValue(sourceObject, null).ToString();
                                if (enuValue.ToLower().Equals("null"))
                                {
                                    destp.SetValue(destinationObject, null, null);
                                }
                                else
                                {
                                    destp.SetValue(destinationObject, enuValue, null);
                                }
                            }
                            else
                            {
                                destp.SetValue(destinationObject, fromp.GetValue(sourceObject, null), null);
                            }
                        });
            }
        }

        /// <summary>
        /// The find class multiple attributes.
        /// </summary>
        /// <param name="t">
        /// The t.
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// </returns>
        public static T[] FindClassMultipleAttributes<T>(Type t)
        {
            var ta = t.GetCustomAttributes(typeof(T), false);
            IList<T> ts = new List<T>();
            foreach (T tt in ta)
            {
                ts.Add(tt);
            }

            return ts.ToArray();
        }

        /// <summary>
        /// Find the attributes of type T inside the type t
        /// </summary>
        /// <typeparam name="T">
        /// </typeparam>
        /// <param name="t">
        /// </param>
        /// <returns>
        /// </returns>
        public static T FindClassUniqueAttribute<T>(Type t)
        {
            var ta = t.GetCustomAttributes(typeof(T), false);
            if (ta.Length == 1)
            {
                return (T)ta[0];
            }

            return default(T);
        }

        /// <summary>
        /// The get property.
        /// </summary>
        /// <param name="instance">
        /// The instance.
        /// </param>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        /// <returns>
        /// The get property.
        /// </returns>
        public static object GetProperty(object instance, string propertyName)
        {
            var t = instance.GetType();
            var prop = t.GetProperty(propertyName);
            if (prop != null)
            {
                return prop.GetValue(instance, null);
            }

            return null;
        }

        /// <summary>
        /// The get read properties.
        /// </summary>
        /// <param name="instance">
        /// The instance.
        /// </param>
        /// <returns>
        /// </returns>
        public static PropertyInfo[] GetReadProperties(object instance)
        {
            // All properties of this instrument
            var allProperties = instance.GetType().GetProperties();

            // Keep a property if CanRead and CanWrite. Linq to objects
            var properties = from p in allProperties where p.CanRead select p;

            return properties.ToArray();
        }

        /// <summary>
        /// The get read write properties.
        /// </summary>
        /// <param name="instance">
        /// The instance.
        /// </param>
        /// <returns>
        /// </returns>
        public static PropertyInfo[] GetReadWriteProperties(object instance)
        {
            // All properties of this instrument
            var allProperties = instance.GetType().GetProperties();

            // Keep a property if CanRead and CanWrite. Linq to objects
            var properties = from p in allProperties where p.CanRead && p.CanWrite select p;

            return properties.ToArray();
        }

        /// <summary>
        /// Find if some type implements an interface
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="interfaceName">
        /// The interface name.
        /// </param>
        /// <returns>
        /// True if type implements interfaceName. False otherwise.
        /// </returns>
        public static bool ImplementsInterface(Type type, string interfaceName)
        {
            return type.GetInterface(interfaceName) != null;
        }

        /// <summary>
        /// Find if some type derives of another
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="baseType">
        /// The base type name.
        /// </param>
        /// <returns>
        /// True if type derives of baseType. False otherwise.
        /// </returns>
        public static bool InheritsOf(Type type, string baseType)
        {
            if (type == null || type.BaseType == null || baseType == null)
            {
                return false;
            }

            return type.BaseType.FullName == baseType;
        }

        /// <summary>
        /// The list app domains.
        /// </summary>
        /// <returns>
        /// </returns>
        public static IList ListAppDomains()
        {
            var host = new CorRuntimeHostClass();
            try
            {
                var list = new ArrayList();
                IntPtr enumHandle;
                host.EnumDomains(out enumHandle);
                while (true)
                {
                    object domain;
                    host.NextDomain(enumHandle, out domain);
                    if (domain == null)
                    {
                        break;
                    }

                    list.Add((AppDomain)domain);
                }

                host.CloseEnum(enumHandle);
                return list;
            }
            finally
            {
                Marshal.ReleaseComObject(host);
            }
        }

        /// <summary>
        /// Set some property value on given instance, return true only if success and previous value wasn't the same
        /// </summary>
        /// <param name="instance">
        /// </param>
        /// <param name="propertyName">
        /// The PropertyName.
        /// </param>
        /// <param name="propertyValue">
        /// The propertyValue.
        /// </param>
        /// <returns>
        /// The set property.
        /// </returns>
        public static bool SetProperty(object instance, string propertyName, object propertyValue)
        {
            var t = instance.GetType();
            var prop = t.GetProperty(propertyName);
            if (prop != null)
            {
                var oldValue = prop.GetValue(instance, null);
                var newValue = propertyValue;

                if (oldValue == propertyValue)
                {
                    newValue = null;
                }

                if (oldValue is IComparable)
                {
                    var c = oldValue as IComparable;
                    if (c.CompareTo(newValue) == 0)
                    {
                        newValue = null;
                    }
                }

                if (oldValue != null && oldValue.Equals(newValue))
                {
                    newValue = null;
                }

                if (newValue != null)
                {
                    prop.SetValue(instance, newValue, null);
                }

                return true;
            }

            return false;
        }

        /// <summary>
        /// Sets a property on a given instance using the content of a table cell
        /// The setter is not called if both contents are equal (==, IComparable and Equals)
        /// </summary>
        /// <param name="instance">
        /// The instance.
        /// </param>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        /// <param name="cell">
        /// The cell.
        /// </param>
        /// <param name="valueChanged">
        /// Is true only if the value of the property has changed
        /// </param>
        /// <returns>
        /// True if the setter could be called directly or after an implicit conversion to property type, false otherwise
        /// </returns>
        public static bool SetProperty(object instance, string propertyName, Table.Cell cell, out bool valueChanged)
        {
            var t = instance.GetType();
            var prop = t.GetProperty(propertyName);
            if (prop != null)
            {
                var oldValue = prop.GetValue(instance, null);

                // Don't use setter if equals (==)
                if (oldValue == cell.Content)
                {
                    valueChanged = false;
                    return true;
                }

                // Don't use setter if equals (IComparable)
                // if (oldValue is IComparable)
                // {
                // var c = oldValue as IComparable;
                // if (c.GetType() == cell.Content.GetType() && c.CompareTo(cell.Content) == 0)
                // {
                // valueChanged = false;
                // return true;
                // }
                // }

                // Don't use setter if equals (Equals)
                if (oldValue != null && oldValue.Equals(cell.Content))
                {
                    valueChanged = false;
                    return true;
                }

                // Same type, call setter directly
                if (cell.GetType() == prop.PropertyType)
                {
                    prop.SetValue(instance, cell, null);
                    valueChanged = true;
                    return true;
                }

                // Search implicit conversion operator (on the cell) able to convert to prop.PropertyType
                var convertors = from m in cell.GetType().GetMethods() where m.Name == "op_Implicit" where m.ReturnType == prop.PropertyType select m;

                // None found
                if (convertors.Count() == 0)
                {
                    valueChanged = false;
                    return false;
                }

                // Try to call conversion operator
                try
                {
                    var convertor = convertors.First();
                    var converted = convertor.Invoke(null, new object[] { cell });
                    prop.SetValue(instance, converted, null);
                    valueChanged = true;
                    return true;
                }
                catch
                {
                    valueChanged = false;
                    return false;
                }
            }

            // Property was not found
            valueChanged = false;
            return false;
        }

        /// <summary>
        /// Filter the types and return only those who implements given interface
        /// </summary>
        /// <param name="interfaceType">
        /// </param>
        /// <param name="types">
        /// </param>
        /// <param name="onlyClasses">
        /// The onlyClasses.
        /// </param>
        /// <returns>
        /// </returns>
        public static Type[] TypesImplementingInterface(Type interfaceType, Type[] types, bool onlyClasses)
        {
            TypeFilter filter = delegate(Type type, object criteriaObj)
                {
                    var criteriaType = criteriaObj as Type;
                    if (criteriaType == null)
                    {
                        return false;
                    }

                    // if both types are the same or if type is a subtype of criteriaType
                    if (type == criteriaType || type.GetInterface(criteriaType.FullName) != null)
                    {
                        return true;
                    }

                    return false;
                };

            var filteredTypes = new List<Type>();
            foreach (var t in types)
            {
                var interfaces = t.FindInterfaces(filter, interfaceType);

                if (interfaces.Length > 0)
                {
                    if (onlyClasses && !t.IsClass)
                    {
                        continue;
                    }
                    else
                    {
                        filteredTypes.Add(t);
                    }
                }
            }

            return filteredTypes.ToArray();
        }

        #endregion
    }
}